package SG3D

import (
	. "SGEngine"
	//"fmt"
	"github.com/gl"
	"github.com/glfw"
	"github.com/glu"
	"math"
)

type Camera3D struct {
	U      Vec3
	V      Vec3
	N      Vec3
	UP     Vec3
	Mat    Mat4
	EyePos Vec3
	Center Vec3
	BaseCamera
	LastPos Vec3
}

func NewCamera3D() ICamera {
	Rcam := new(Camera3D)
	Rcam.CamScene = WinRoot.MainScene
	SGRenderQueue = NewRenderQueue()
	Rcam.EyePos = Vec3{0, 0, 10}
	Rcam.Center = Vec3{0, 0, -10}
	Rcam.UP = Vec3{0, 1, 0}
	Rcam.N = Vec3{Rcam.EyePos.X - Rcam.Center.X, Rcam.EyePos.Y - Rcam.Center.Y, Rcam.EyePos.Z - Rcam.Center.Z}
	Rcam.U = Vec3{Rcam.UP.Cross(&Rcam.N).X, Rcam.UP.Cross(&Rcam.N).Y, Rcam.UP.Cross(&Rcam.N).Z}
	Rcam.V = Vec3{Rcam.N.Cross(&Rcam.U).X, Rcam.N.Cross(&Rcam.U).Y, Rcam.N.Cross(&Rcam.U).Z}

	Rcam.N.Normalize()
	Rcam.U.Normalize()
	Rcam.V.Normalize()
	RegisterEvent(Rcam.CamScene, EVENT_MOUSEMOVE, Rcam.MouseMove)
	ACTimeUpdate.AddUpdate(Rcam.tUpdate)

	return Rcam
}

func (c *Camera3D) tUpdate(dt float64, this interface{}) {
	if glfw.Key(65) == 1 {
		sn := c.U.Scaled(0.03)
		c.EyePos.Add(sn.Negated())
	}
	if glfw.Key(68) == 1 {
		sn := c.U.Scaled(0.03)
		c.EyePos.Add(sn)
	}
	if glfw.Key(87) == 1 {
		sn := c.N.Scaled(0.03)
		c.EyePos.Add(sn.Negated())
	}
	if glfw.Key(83) == 1 {
		sn := c.N.Scaled(0.03)
		c.EyePos.Add(sn)
	}
}

func (c *Camera3D) setModelViewMatrix() {
	c.Mat[0], c.Mat[4], c.Mat[8], c.Mat[12] = c.U.X, c.U.Y, c.U.Z, -c.EyePos.Dot(&c.U)
	c.Mat[1], c.Mat[5], c.Mat[9], c.Mat[13] = c.V.X, c.V.Y, c.V.Z, -c.EyePos.Dot(&c.V)
	c.Mat[2], c.Mat[6], c.Mat[10], c.Mat[14] = c.N.X, c.N.Y, c.N.Z, -c.EyePos.Dot(&c.N)
	c.Mat[3], c.Mat[7], c.Mat[11], c.Mat[15] = 0, 0, 0, 1
	gl.MatrixMode(gl.MODELVIEW)
	gl.LoadIdentity()
	gl.LoadMatrixd((*[16]float64)(&c.Mat))
	//fmt.Println((*[16]float64)(&c.Mat))
}

func (c *Camera3D) SetCamera() {
	c.setShape(45, float64(c.CamScene.SceneWidth)/float64(c.CamScene.SceneHeight), 0.1, 100)
	gl.Enable(gl.DEPTH_TEST)
	gl.DepthFunc(gl.LEQUAL)
	gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
	gl.ClearColor(gl.GLclampf(c.CamScene.BgColor.R), gl.GLclampf(c.CamScene.BgColor.G), gl.GLclampf(c.CamScene.BgColor.B), 1)
	gl.ClearDepth(1)

	//glu.LookAt(float64(c.EyePos.X), float64(c.EyePos.Y), float64(c.EyePos.Z), float64(c.Center.X), float64(c.Center.Y), float64(c.Center.Z), 0.0, 1.0, 0.0)
}

func (Cam *Camera3D) RenderNodes(PNode INode) {
	SGRenderQueue.Clear()
	if Cam.RenderLayer()&PNode.GetLayer() > 0 || PNode.GetLayer() == 0 && PNode.GetNode().Enable == true {
		SGRenderQueue.AddToQueue(PNode)
		C := *PNode.Childrens()
		for i := 0; i < C.GetSize(); i++ {
			Cam.RenderNodes(C.I(i).(INode))
		}
	}

	Cam.setModelViewMatrix()
	SGRenderQueue.Render()

}

func (c *Camera3D) setShape(viewAngle float64, aspect float64, Near float64, Far float64) {
	gl.MatrixMode(gl.PROJECTION)
	gl.LoadIdentity()
	glu.Perspective(viewAngle, aspect, Near, Far)
}

func (c *Camera3D) MouseMove(X, Y int, node *EventNode) {
	dx := float64(X) - c.LastPos.X
	dy := float64(Y) - c.LastPos.Y
	if glfw.MouseButton(glfw.MouseRight) == 1 {
		c.RotateX(dx * 0.1)
		c.RotateY(dy * 0.1)
	}
	if glfw.MouseButton(glfw.MouseLeft) == 1 {
		c.Pitch(dy * 0.1)
		c.Yaw(dx * 0.1)
	}

	//c.RotateY(dy * 0.03)
	c.LastPos.X = float64(X)
	c.LastPos.Y = float64(Y)
}

func (c *Camera3D) GetDist() float64 {
	Dist := math.Pow(c.EyePos.X, 2) + math.Pow(c.EyePos.Y, 2) + math.Pow(c.EyePos.Z, 2)
	return math.Pow(Dist, 0.5)
}
func (c *Camera3D) Yaw(angle float64) {
	cs := math.Cos(angle * 3.14159265 / 180)
	sn := math.Sin(angle * 3.14159265 / 180)
	t := Vec3{c.N.X, c.N.Y, c.N.Z}
	s := Vec3{c.U.X, c.U.Y, c.U.Z}
	c.N.X = cs*t.X - sn*s.X
	c.N.Y = cs*t.Y - sn*s.Y
	c.N.Z = cs*t.Z - sn*s.Z

	c.U.X = sn*t.X + cs*s.X
	c.U.Y = sn*t.Y + cs*s.Y
	c.U.Z = sn*t.Z + cs*s.Z
	//c.setModelViewMatrix()
}

func (c *Camera3D) Pitch(angle float64) {
	cs := math.Cos(angle * 3.14159265 / 180)
	sn := math.Sin(angle * 3.14159265 / 180)
	t := Vec3{c.V.X, c.V.Y, c.V.Z}
	s := Vec3{c.N.X, c.N.Y, c.N.Z}
	c.V.X = cs*t.X - sn*s.X
	c.V.Y = cs*t.Y - sn*s.Y
	c.V.Z = cs*t.Z - sn*s.Z

	c.N.X = sn*t.X + cs*s.X
	c.N.Y = sn*t.Y + cs*s.Y
	c.N.Z = sn*t.Z + cs*s.Z
	//c.setModelViewMatrix()
}

func (c *Camera3D) Roll(angle float64) {
	cs := math.Cos(angle * 3.14159265 / 180)
	sn := math.Sin(angle * 3.14159265 / 180)
	t := Vec3{c.U.X, c.U.Y, c.U.Z}
	s := Vec3{c.V.X, c.V.Y, c.V.Z}
	c.U.X = cs*t.X - sn*s.X
	c.U.Y = cs*t.Y - sn*s.Y
	c.U.Z = cs*t.Z - sn*s.Z

	c.V.X = sn*t.X + cs*s.X
	c.V.Y = sn*t.Y + cs*s.Y
	c.V.Z = sn*t.Z + cs*s.Z
	//c.setModelViewMatrix()
}

func (c *Camera3D) Slide(du, dv, dn float64) {
	c.EyePos.X = c.EyePos.X + du*c.U.X + dv*c.V.X + dn*c.N.X
	c.EyePos.Y = c.EyePos.Y + du*c.U.Y + dv*c.V.Y + dn*c.N.Y
	c.EyePos.Z = c.EyePos.Z + du*c.U.Z + dv*c.V.Z + dn*c.N.Z

	c.Center.X = c.Center.X + du*c.U.X + dv*c.V.X + dn*c.N.X
	c.Center.Y = c.Center.X + du*c.U.Y + dv*c.V.Y + dn*c.N.Y
	c.Center.Z = c.Center.X + du*c.U.Z + dv*c.V.Z + dn*c.N.Z
	//c.setModelViewMatrix()
}

func (c *Camera3D) RotateX(angle float64) {
	d := c.GetDist()
	cnt := 1000.0
	theta := angle / cnt
	slide_d := -2 * d * math.Sin(theta*3.14159265/360)
	c.Yaw(theta / 2)
	for ; cnt != 0; cnt -= 1 {
		c.Slide(slide_d, 0, 0)
		c.Yaw(theta)
	}
}

func (c *Camera3D) RotateY(angle float64) {
	d := c.GetDist()
	cnt := 100.0
	theta := angle / cnt
	slide_d := 2 * d * math.Sin(theta*3.1415926/360)
	c.Pitch(theta / 1)
	for ; cnt != 0; cnt -= 1 {
		c.Slide(0, slide_d, 0)
		c.Pitch(theta)
	}
	c.Pitch(-theta / 2)
}
